/*
 * Copyright (c) 2009, The MilkyTracker Team.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - Neither the name of the <ORGANIZATION> nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 *  ChannelMixer.h
 *  MilkyPlay
 *
 *  Created by Peter Barth on Tue Oct 19 2004.
 *
 *  This class is pretty much emulating a Gravis Ultrasound with a timer set to 250Hz
 *  i.e. mixerHandler() will call a timer routine 250 times per second while mixing
 *  the audio stream in between.
 */
#ifndef __CHANNELMIXER_H__
#define __CHANNELMIXER_H__

#include "MilkyPlayCommon.h"
#include "AudioDriverBase.h"
#include "Mixable.h"

#define MP_FP_CEIL(x)           (((x) + 65535) >> 16)
#define MP_FP_MUL(a, b)         ((mp_sint32)(((mp_int64)(a) * (mp_int64)(b)) >> 16))

#define MP_INCREASESMPPOS(intpart, fracpart, fp, fractbits) \
    intpart += ((fp) >> (fractbits)); \
    fracpart += ((fp) & (1 << (fractbits)) - 1); \
    if (((fracpart) >> (fractbits)) == 1) \
    { \
        intpart++; \
        fracpart &= (1 << (fractbits)) - 1; \
    }

class ChannelMixer;
typedef void (ChannelMixer::* TSetFreq)(mp_sint32 c, mp_sint32 f);

class MixerSettings
{
    protected:
        enum
        {
            NUMRESAMPLERTYPES = 21,
        };
    public:
        // These are arranged in a way, so that the ramping flag toggles with the
        // LSB (bit 0) if you count through them
        enum ResamplerTypes
        {
            MIXER_NORMAL,
            MIXER_NORMAL_RAMPING,

            MIXER_LERPING,
            MIXER_LERPING_RAMPING,

            MIXER_LAGRANGE,
            MIXER_LAGRANGE_RAMPING,

            MIXER_SPLINE,
            MIXER_SPLINE_RAMPING,

            MIXER_SINCTABLE,
            MIXER_SINCTABLE_RAMPING,

            MIXER_SINC,
            MIXER_SINC_RAMPING,

            MIXER_AMIGA500,
            MIXER_AMIGA500_RAMPING,

            MIXER_AMIGA500LED,
            MIXER_AMIGA500LED_RAMPING,

            MIXER_AMIGA1200,
            MIXER_AMIGA1200_RAMPING,

            MIXER_AMIGA1200LED,
            MIXER_AMIGA1200LED_RAMPING,

            MIXER_DUMMY,

            MIXER_INVALID
        };

        enum
        {
            // pretty large buffer for most systems
            BUFFERSIZE_DEFAULT = 8192
        };
};

class ChannelMixer : public MixerSettings, public Mixable
{
    public:
        enum
        {
            // This is the basis for timing & mixing
            // 250hz timer
            MP_TIMERFREQ = 250,
            MP_BASEFREQ = 48000,        // is chosen because (48000 % 250) == 0
            // period in samples for MP_TIMERFREQ
            MP_BEATLENGTH = (MP_BASEFREQ / MP_TIMERFREQ),
            // mixer state flags
            MP_SAMPLE_FILTERLP = 65536,
            MP_SAMPLE_MUTE = 32768,
            MP_SAMPLE_ONESHOT = 8192,
            MP_SAMPLE_FADEOFF = 4096,
            MP_SAMPLE_FADEOUT = 2048,
            MP_SAMPLE_FADEIN = 1024,
            MP_SAMPLE_PLAY = 256,
            MP_SAMPLE_BACKWARD = 128,

            MP_INVALID_VALUE = 0x7FFFFFFF,
            MP_FILTERPRECISION = 8
        };

        static inline mp_sint32 fixedmul(mp_sint32 a, mp_sint32 b) {
            return MP_FP_MUL(a, b);
        }
        static inline mp_sint32 fixeddiv(mp_sint32 a, mp_sint32 b) {
            return ((mp_sint32)(((mp_int64)(a) * 65536 / (mp_int64)(b))));
        }

        // this is a subset of the channel state which is stored along
        // with the time progress, so you can do a look up of the state
        // even with large buffer sizes
        struct TTimeRecord
        {
            mp_uint32 flags;                        // bit 8 = sample played
                                                    // bit 9 = sample direction (0 = forward, 1 = backward)
                                                    // bit 10-11 = sample ticker used to represent ramping states
                                                    // bit 12 = scheduled to stop
                                                    // bit 13 = one shot looping
                                                    // bit 15 = mute channel
            const mp_sbyte* sample;                 // pointer to sample
            mp_sint32 smppos;                       // 32 bit integer part of sample position
            mp_sint32 volPan;                       // 32 bits, upper 16 bits = pan, lower 16 bits = vol
            mp_sint32 smplen;
            mp_sint32 smpposfrac;                   // 16 bit fractional part of sample position
            mp_sint32 smpadd;                       // 16:16 fixedpoint increment
            mp_sint32 loopend;
            mp_sint32 loopstart;
            mp_sint32 fixedtime;                    // for amiga resampler (running time)
            mp_sint32 fixedtimefrac;                // for sinc/amiga resamplers (running time fraction)

            TTimeRecord () :
                flags(0),
                sample(NULL),
                smppos(0),
                volPan(0),
                smplen(0),
                smpposfrac(0),
                smpadd(0),
                loopend(0),
                loopstart(0),
                fixedtime(0),
                fixedtimefrac(0)
            {
            }
        };

        struct TMixerChannel
        {
            mp_uint32 flags;                        // bit 8 = sample played
                                                    // bit 9 = sample direction (0 = forward, 1 = backward)
                                                    // bit 10-11 = sample ticker used to represent ramping states
                                                    // bit 12 = scheduled to stop
                                                    // bit 13 = one shot looping
                                                    // bit 15 = mute channel
            const mp_sbyte* sample;                 // pointer to sample
            mp_sint32 smplen;
            mp_sint32 smppos;                       // 32 bit integer part of sample position
            mp_sint32 smpposfrac;                   // 16 bit fractional part of sample position
            mp_sint32 smpadd;                       // 16:16 fixed point increment
            mp_sint32 rsmpadd;                      // fixed point reciprocal of the increment
            mp_sint32 loopend;                      // loop end
            mp_sint32 loopendcopy;                  // Temporary placeholder for one-shot looping
            mp_sint32 loopstart;                    // loop start

            mp_sint32 finalvolr;
            mp_sint32 finalvoll;

            mp_sint32 vol;
            mp_sint32 pan;

            mp_sint32 rampFromVolStepR;
            mp_sint32 rampFromVolStepL;

            mp_sint32 a, b, c;                      // Filter coefficients
            mp_sint32 currsample;                   // sample history for filtering
            mp_sint32 prevsample;                   // see above

            mp_sint32 cutoff;
            mp_sint32 resonance;

            mp_sint32 fixedtime;                    // for amiga resampler (running time)
            mp_sint32 fixedtimefrac;                // for sinc/amiga resamplers (running time fraction)

            mp_uint32 timeRecordSize;
            TTimeRecord* timeRecord;
            mp_sint32 index;                        // For Amiga resampler

            TMixerChannel () :
                timeRecordSize(0),
                timeRecord(NULL)
            {
                clear();
            }

            TMixerChannel (bool fastContruction) :
                timeRecordSize(0),
                timeRecord(NULL)
            {
            }

            ~TMixerChannel ()
            {
                if (timeRecord)
                    delete[] timeRecord;
            }

            void clear()
            {
                flags = 0;
                sample = NULL;
                smplen = 0;
                smppos = 0;
                smpposfrac = 0;
                smpadd = 0;
                rsmpadd = 0;
                loopend = 0;
                loopendcopy = 0;
                loopstart = 0;

                finalvolr = 0;
                finalvoll = 0;

                vol = 0;
                pan = 128;

                rampFromVolStepR = 0;
                rampFromVolStepL = 0;

                a = b = c = 0;
                currsample = 0;
                prevsample = 0;

                cutoff = MP_INVALID_VALUE;
                resonance = MP_INVALID_VALUE;

                fixedtime = 0;
                fixedtimefrac = 0;
                index = -1;                 // is filled during runtime

                if (timeRecord)
                    memset(timeRecord, 0, sizeof(TTimeRecord) * timeRecordSize);
            }

            void reallocTimeRecord(mp_uint32 size)
            {
                delete[] timeRecord;
                timeRecordSize = size;
                timeRecord = new TTimeRecord[size];
            }
        };

        class ResamplerBase
        {
            private:
                // add channels without volume ramping
                void addChannelsNormal(ChannelMixer* mixer, mp_uint32 numChannels, mp_sint32* buffer32, mp_sint32 beatNum, mp_sint32 beatlength);
                // add channels with volume ramping
                void addChannelsRamping(ChannelMixer* mixer, mp_uint32 numChannels, mp_sint32* buffer32, mp_sint32 beatNum, mp_sint32 beatlength);
            public:
                virtual ~ResamplerBase ()
                {
                }

                void addChannels(ChannelMixer* mixer, mp_uint32 numChannels, mp_sint32* buffer32, mp_sint32 beatNum, mp_sint32 beatlength);
                void addChannel(TMixerChannel* chn, mp_sint32* buffer32, const mp_sint32 beatlength, const mp_sint32 beatSize);

                // walk along the sample
                // intpart is the 32 bit integer part of the position
                // fracpart is the fractional part of the position
                // fp is the amount of samples to advance
                // is the resolution of the fractional part in bits (default is 16)
                static inline void advanceSamplePosition(mp_sint32& intpart,
                                                         mp_sint32& fracpart,
                                                         const mp_sint32 fp,
                                                         const mp_sint32 fracbits = 16)
                {
                    MP_INCREASESMPPOS(intpart, fracpart, fp, fracbits);
                }

                // if this resampler is doing ramping
                virtual bool isRamping() = 0;
                // this resampler can perform a normal block add to the mixing buffer
                virtual bool supportsNoChecking() = 0;
                // optional: if this resampler is able to perform a full checked walk along the sample
                virtual bool supportsFullChecking() = 0;

                // see above, you will need to implement at least one of the following
                virtual void addBlockNoCheck(mp_sint32* buffer, TMixerChannel* chn, mp_uint32 count)
                {
                    // if this is called your own derived resampler is not properly working
                    ASSERT(false);
                }

                // see above for comments
                virtual void addBlockFull(mp_sint32* buffer, TMixerChannel* chn, mp_uint32 count)
                {
                    ASSERT(false);
                }

                // in case the resampler needs to get hold of the current mixing frequency
                virtual void setFrequency(mp_sint32 frequency) {
                }

                // in case the resampler needs to get hold of the current number of channels
                virtual void setNumChannels(mp_sint32 num) {
                }
        };

        friend class ChannelMixer::ResamplerBase;
    private:
        mp_uint32 mixerNumAllocatedChannels; // Number of channels to be allocated by mixer
        mp_uint32 mixerNumActiveChannels;   // Number of channels to be mixed
        mp_uint32 mixerLastNumAllocatedChannels;

        mp_uint32 mixFrequency;             // Mixing frequency
        mp_uint32 rMixFrequency;            // 0x7FFFFFFF/mixFrequency

        mp_sint32* mixbuffBeatPacket;
        mp_uint32 mixBufferSize;            // this is the resulting buffer size in 16 bit words

        mp_uint32 beatPacketSize;           // size of 1/250 of a second in samples
        mp_uint32 numBeatPackets;           // how many of these fit in our buffer size
        mp_uint32 lastBeatRemainder;        // used while filling the buffer, if the buffer is not an exact multiple of beatPacketSize

        TMixerChannel* channel;
        TMixerChannel* newChannel;

        mp_sint32 masterVolume;             // mixer master volume
        mp_sint32 panningSeparation;        // panning separation from 0 (mono) to 256 (full stereo)

        ResamplerTypes resamplerType;
        TSetFreq setFreqFuncTable[NUMRESAMPLERTYPES];               // If different precisions are used, use other frequency calculation procedures
        ResamplerBase* resamplerTable[NUMRESAMPLERTYPES];

        bool paused;
        bool disableMixing;
        bool allowFilters;

        void setFrequency(mp_sint32 frequency);

        void mixBeatPacket(mp_uint32 numChannels,
                           mp_sint32* buffer32,
                           mp_sint32 beatPacketIndex,
                           mp_sint32 beatPacketSize)
        {
            resamplerTable[resamplerType]->addChannels(this, numChannels, buffer32, beatPacketIndex, beatPacketSize);
        }

        inline void timer(mp_uint32 beatIndex)
        {
            timerHandler(beatIndex <= getNumBeatPackets() ? beatIndex : getNumBeatPackets());
        }

        void reallocChannels();
        void clearChannels();
    public:
        ChannelMixer (mp_uint32 numChannels,
                      mp_uint32 frequency);

        virtual ~ChannelMixer ();

        mp_uint32 getMixBufferSize() const {
            return mixBufferSize;
        }
        void mix(mp_sint32* buffer, mp_uint32 numSamples);
        void updateSampleCounter(mp_sint32 numSamples) {
            sampleCounter += numSamples;
        }
        void resetSampleCounter() {
            sampleCounter = 0;
        }

        mp_sint32 initDevice();
        mp_sint32 closeDevice();

        void stop();
        mp_sint32 pause();
        mp_sint32 resume();

        void setDisableMixing(bool disableMixing) {
            this->disableMixing = disableMixing;
        }
        void setAllowFilters(bool allowFilters) {
            this->allowFilters = allowFilters;
        }
        bool getAllowFilters() const {
            return allowFilters;
        }

        void resetChannelsFull();
        void resetChannelsWithoutMuting();

        void setResamplerType(ResamplerTypes type);
        ResamplerTypes getResamplerType() const {
            return resamplerType;
        }
        bool isRamping()  const {
            return resamplerTable[resamplerType]->isRamping();
        }

        virtual mp_sint32 adjustFrequency(mp_uint32 frequency);
        mp_sint32 getMixFrequency() {
            return mixFrequency;
        }

        static mp_sint32 beatPacketsToBufferSize(mp_uint32 mixFrequency, mp_uint32 numBeats);
        virtual mp_sint32 setBufferSize(mp_uint32 bufferSize);

        mp_uint32 getBeatPacketSize() const {
            return beatPacketSize;
        }
        mp_uint32 getNumBeatPackets() const {
            return mixBufferSize / beatPacketSize;
        }

        // volume control
        void setMasterVolume(mp_sint32 vol) {
            masterVolume = vol;
        }
        mp_sint32 getMasterVolume() const {
            return masterVolume;
        }
        // panning control
        void setPanningSeparation(mp_sint32 separation) {
            panningSeparation = separation;
        }
        mp_sint32 getPanningSeparation() const {
            return panningSeparation;
        }

        bool isInitialized() const {
            return initialized;
        }
        bool isPlaying() const {
            return startPlay;
        }

        mp_sint32 getNumActiveChannels();
        mp_sint32 getNumAllocatedChannels() const {
            return mixerNumActiveChannels;
        }

        mp_int64 getSampleCounter() const {
            return sampleCounter;
        }

        mp_sint32 getBeatIndexFromSamplePos(mp_uint32 smpPos) const;

        ResamplerBase * getCurrentResampler() const {
            return resamplerTable[resamplerType];
        }
    protected:
        bool initialized;
        bool startPlay;

        mp_int64 sampleCounter;             // number of samples played (per song)

        void startMixer()
        {
            lastBeatRemainder = 0;
        }

        void setNumChannels(mp_uint32 num);

        void setActiveChannels(mp_uint32 num);
    public:
        void setVol(mp_sint32 c, mp_sint32 v) {
            channel[c].vol = v;
        }
        mp_sint32 getVol(mp_sint32 c) {
            return channel[c].vol;
        }

        void setPan(mp_sint32 c, mp_sint32 p) {
            channel[c].pan = p;
        }

        void setFreq(mp_sint32 c, mp_sint32 f)
        {
            (this->*setFreqFuncTable[resamplerType])(c, f);
        }

        // Default low precision calculations
        void setChannelFrequency(mp_sint32 c, mp_sint32 f);

        void setFilterAttributes(mp_sint32 c, mp_sint32 cutoff, mp_sint32 resonance);

        void playSample(mp_sint32 c,        // channel
                        mp_sbyte* smp,        // sample buffer
                        mp_sint32 smplen,        // sample size
                        mp_sint32 smpoffs,        // sample offset
                        mp_sint32 smpoffsfrac,
                        bool smpOffsetWrap,
                        mp_sint32 lstart,        // loop start
                        mp_sint32 len,        // loop end
                        mp_sint32 flags,
                        bool ramp = true);

        bool isChannelPlaying(mp_sint32 c) {
            return (channel[c].flags & MP_SAMPLE_PLAY) != 0;
        }

        void stopSample(mp_sint32 c) {
            channel[c].flags &= ~(MP_SAMPLE_FADEIN | MP_SAMPLE_FADEOUT); channel[c].flags |= MP_SAMPLE_FADEOFF;
        }

        void breakLoop(mp_sint32 c) {
            channel[c].flags &= ~3; channel[c].loopend = channel[c].smplen;
        }

        // handle with care
        void setSamplePos(mp_sint32 c, mp_sint32 pos) {
            channel[c].smppos = pos; channel[c].smpposfrac = 0;
        }

        mp_sint32 getSamplePos(mp_sint32 c) {
            return channel[c].smppos;
        }
        mp_sint32 getSamplePosFrac(mp_sint32 c) {
            return channel[c].smpposfrac;
        }

        void setBackward(mp_sint32 c)
        {
            if (channel[c].flags & MP_SAMPLE_PLAY)
            {
                channel[c].flags |= MP_SAMPLE_BACKWARD;
                if (channel[c].smppos < channel[c].loopstart)
                {
                    channel[c].smppos = channel[c].loopend;
                }
            }
        }

        void setForward(mp_sint32 c)
        {
            if (channel[c].flags & MP_SAMPLE_PLAY)
            {
                channel[c].flags &= ~MP_SAMPLE_BACKWARD;
                if (channel[c].smppos > channel[c].loopend)
                {
                    channel[c].smppos = channel[c].loopstart;
                }
            }
        }

        void muteChannel(mp_sint32 c, bool m);

        bool isChannelMuted(mp_sint32 c);

        mp_uint32 getSyncSampleCounter();
    protected:
        // timer procedure for mixing
        virtual void timerHandler(mp_sint32 currentBeatPacket) = 0;

        #ifdef MILKYTRACKER
        friend class PlayerController;
        #endif

        #ifdef __MPTIMETRACKING__
    public:
        void mixData(mp_sint32 c,
                     mp_sint32* buffer,
                     mp_sint32 count,
                     mp_sint32 sampleShift,
                     mp_sint32 fMul = 0,
                     mp_sint32 bufferIndex = -1,
                     mp_sint32 packetIndex = -1) const;
        #endif
};

#endif